#include "gameclient.h"
#include "cardset.h"

#include <QDebug>
#include <QJsonArray>
#include <QJsonDocument>

#include <algorithm>
#include <functional>

GameClient::GameClient() : socket(nullptr)
{
    resetState();
}

void GameClient::resetState()
{
    state = GameState::waiting;
    winner = -1;
    dataReady = false;
    postIllegal = false;
    memset(selected, 0, sizeof(selected));
    // win, mustPost: no need to initialize explicitly
    // should JSON object be cleared?
}

bool GameClient::connectToServer(const QHostAddress& serverAddress, quint16 serverPort)
{
    socket = new QTcpSocket;
    socket->connectToHost(serverAddress, serverPort);
    if (!socket->waitForConnected(1000)) {
        qDebug() << "[C] failed to connect server: " << serverAddress << ":" << serverPort;
        return false;
    }

    connect(socket, &QTcpSocket::readyRead, this, &GameClient::onReadyRead);
    connect(socket, &QTcpSocket::disconnected, this, &GameClient::onServerDisconnected);
    qDebug() << "[C] connection established with server";
    return true;
}

void GameClient::stop()
{
    if (socket) {
        socket->disconnect();
        socket->close();
        garbage.add(socket); // delayed delete
        socket = nullptr;
        qDebug("[C] client stopped");
    }
}

void GameClient::action(Message& m, const QByteArray& s)
{
    if (m.status != MSG_OK) {
        qDebug("[C] invalid message from server, status = %#x", m.status);
        emit clientStopping("connection failed");
        return;
    }

    if (m.type & MSG_STRING) {
        qDebug() << "[C] message:" << s;
        m.type &= ~MSG_STRING;
    }

    switch (m.type) {
    case MSG_REJECT: {
        qDebug("[C] connection rejected by server");
        emit clientStopping("rejected by server");
        break;
    }
    case MSG_GAME_QUIT: {
        qDebug("[C] MSG_GAME_QUIT received");
        emit clientStopping(s);
        break;
    }
    case MSG_GAME_START: {
        qDebug("[C] MSG_GAME_START received");
        sendMsgW(socket, MSG_ACK_GAME);
        emit startGame();
        break;
    }
    case MSG_GAME_OVER: {
        state = GameState::over;
        winner = m.data;
        qDebug("[C] Game Over! winnner: %d", winner);
        emit clientStateChanged();
        break;
    }
    case MSG_QUERY_PREEMPT: {
        state = GameState::preempting;
        unpackMessage(s);
        emit clientStateChanged();
        break;
    }
    case MSG_REQUEST_POST: {
        state = GameState::started;
        mustPost = m.data > 0;
        unpackMessage(s);
        emit clientStateChanged();
        break;
    }
    case MSG_GAME_INFO: {
        unpackMessage(s);
        emit clientUpdated();
        break;
    }
    }
}

bool GameClient::judgeWinner(int id)
{
    if (id < 0)
        id = go["id"].toInt();
    int landlord = go["landlord"].toInt();
    return !((id == landlord) ^ (winner == landlord));
}

void GameClient::restartGame()
{
    resetState();
    sendMsgW(socket, MSG_ACK_GAME);
}

// value: preempt if true
void GameClient::doPreempt(bool value)
{
    sendMsgW(socket, MSG_REPLY_PREEMPT, value);
}

void GameClient::postCards(bool skip)
{
    if (!skip) {
        QList<Card> toBePosted;
        for (int i = 0; i < handCards.size(); ++i) {
            if (selected[i]) {
                toBePosted.push_back(handCards[i]);
                selected[i] = 0;
            }
        }

        CardSet newSet(toBePosted);
        if (newSet.getType() == "无效") {
            postIllegal = true;
            emit cardsPosted(false);
            return;
        }

        QString prevPostedRepr = go["pcards"].toString();
        // if prevPostedRepr is empty, whatever card set is ok
        if (!prevPostedRepr.isEmpty()) {
            CardSet prevSet(prevPostedRepr);
            if (!newSet.isComparable(prevSet) || !(newSet > prevSet)) {
                postIllegal = true;
                emit cardsPosted(false);
                return;
            }
        }

        postIllegal = false;
        QByteArray postedRepr;
        for (Card card : toBePosted) {
            handCards.erase(std::find(handCards.begin(), handCards.end(), card));
            postedRepr += card.repr();
        }

        sendMsg(socket, MSG_POST_CARDS, postedRepr, 1);
    } else {
        postIllegal = false;
        sendMsgW(socket, MSG_POST_CARDS, 0);
    }

    emit cardsPosted(true);
}

void GameClient::unpackMessage(const QByteArray& s)
{
    go = QJsonDocument::fromJson(s).object();
    // only unpack handcards
    QString cardsRepr = go["hcards"].toString();
    handCards.clear();
    for (int i = 0; i < cardsRepr.size(); ++i)
        handCards.push_back(Card::fromChar(cardsRepr[i].unicode()));

    dataReady = true;
}

void GameClient::onReadyRead()
{
    do {
        QByteArray s;
        Message m = recvMsg(socket, &s);

        action(m, s);
        if (!socket) // stop if socket already destroyed!
            return;
    } while (socket->bytesAvailable());
}

void GameClient::onServerDisconnected()
{
    qDebug("[C] connection with server lost");
    emit clientStopping("connection lost");
}

GameClient::~GameClient()
{
    stop();
}
